package org.jasig.cas.adaptors.jdbc;

import com.github.thinwonton.cas.showcase.common.DigestUtils;
import com.github.thinwonton.cas.showcase.common.PasswordUtils;
import org.jasig.cas.authentication.HandlerResult;
import org.jasig.cas.authentication.PreventedException;
import org.jasig.cas.authentication.UsernamePasswordCredential;
import org.jasig.cas.authentication.principal.SimplePrincipal;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;

import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.FailedLoginException;
import javax.sql.DataSource;
import javax.validation.constraints.NotNull;
import java.security.GeneralSecurityException;
import java.util.Map;

public class QueryAndSaltDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {

    private static final String DEFAULT_PASSWORD_FIELD = "password";
    private static final String DEFAULT_SALT_FIELD = "salt";

    @NotNull
    protected String sql;

    @NotNull
    protected String passwordFieldName = DEFAULT_PASSWORD_FIELD;

    @NotNull
    protected String saltFieldName = DEFAULT_SALT_FIELD;

    @NotNull
    protected String algorithmName;

    public QueryAndSaltDatabaseAuthenticationHandler(final DataSource dataSource,
                                                     final String sql,
                                                     final String algorithmName) {
        super();
        setDataSource(dataSource);
        this.algorithmName = algorithmName;
        this.sql = sql;
    }

    @Override
    protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential)
            throws GeneralSecurityException, PreventedException {
        final String username = credential.getUsername();
        final String encodedPsw = this.getPasswordEncoder().encode(credential.getPassword());

        try {
            //查询数据库的数据
            final Map<String, Object> values = getJdbcTemplate().queryForMap(this.sql, username);

            //效验密码
            final String digestedPassword = digestEncodedPassword(encodedPsw, values);
            if (!values.get(this.passwordFieldName).equals(digestedPassword)) {
                throw new FailedLoginException("Password does not match value on record.");
            }

        } catch (final IncorrectResultSizeDataAccessException e) {
            if (e.getActualSize() == 0) {
                throw new AccountNotFoundException(username + " not found with SQL query");
            } else {
                throw new FailedLoginException("Multiple records found for " + username);
            }
        } catch (final DataAccessException e) {
            throw new PreventedException("SQL exception while executing query for " + username, e);
        }

        return createHandlerResult(credential, new SimplePrincipal(username, null), null);
    }

    protected String digestEncodedPassword(final String encodedPassword, final Map<String, Object> values) {

        if (!values.containsKey(this.saltFieldName)) {
            throw new RuntimeException("Specified field name for salt does not exist in the results");
        }

        final String salt = values.get(this.saltFieldName).toString();
        return PasswordUtils.encryptPassword(encodedPassword, salt, this.algorithmName);
    }

    public final void setPasswordFieldName(final String passwordFieldName) {
        this.passwordFieldName = passwordFieldName;
    }

    public final void setSaltFieldName(final String saltFieldName) {
        this.saltFieldName = saltFieldName;
    }

}